/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Domain;

import Domain.Player.Disc;
import Main.ObjectFileIO;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

/**
 *
 * @author Mattis
 */
public class MonteCarlo implements Serializable {
    /*
     * TODO: kolla om ett tillstånd redan finns sparat och om det gör det använd
     * det och lägg till i visited
     *
     * Övergriplig beskrivning: Låt varje state vara ett bräde(alltså GameGrid),
     * varje action är två heltal som signalerar vilken ruta man valt. Låt
     * endast "sluttillstånden" innehålla rewards, alltså endast när någon
     * vunnit eller brädet är fullt ska innehålla rewards(1 för vinst, 0 för
     * lika och -1 för förlust). på sidan 196 i RL boken pratar dem om
     * afterstates och grejer.... 2.5 pratar dem om att uppdatera(medelvärda)
     * ett state utan att hålla ordning på alla individuella sub states.
     *
     *
     *
     *
     *
     *
     *
     *
     * med en viss sannolikhet välj ett drag för ett redan sparat tillstånd(det
     * bästa av dem som går att nå). Om tillståndet för det resulterande brädet
     * redan finns lagrat så spara detta i arraylisten med de tillstånd som
     * skall uppdateras och öka antalet gånger den besökts med 1. Om det inte
     * finns så skapa det tillstånd som skulle bli om man gjorde ett slumpat
     * drag och spara ner detta till arraylisten. om måltillstånd så backa upp
     * värdena annars repetera.
     */

    private HashMap<String, State> hashmap;
    private ArrayList<State> visited;
    private Random random = new Random();
    private Disc disc;
    private double alpha = 0.1;
    private String fileName = "";

    public MonteCarlo() {
        hashmap = new HashMap<String, State>();
        this.visited = new ArrayList<State>();
    }

    public MonteCarlo(Disc discID) {
        hashmap = new HashMap<String, State>();
        this.visited = new ArrayList<State>();
        this.disc = discID;
    }

    public MonteCarlo(MonteCarlo temp) {
        this.hashmap = temp.getHashMap();
    }

    public MonteCarlo(String fileName) {
        ObjectFileIO fileIO = new ObjectFileIO(fileName);
        MonteCarlo temp = (MonteCarlo) fileIO.readFile();
        this.setHashmap(temp.getHashMap());
    }

    public MonteCarlo(Disc discID, String fileName) {
        /*
         * fileName = fileName + ".txt"; File tempFile = new File(fileName);
         * this.fileName = fileName; if (tempFile.exists()) { ObjectFileIO
         * fileIO = new ObjectFileIO(fileName); MonteCarlo temp = (MonteCarlo)
         * fileIO.readFile(); this.setHashmap(temp.getHashMap()); } else {
         * this.hashmap = new HashMap<String, State>(); }/*
         */
        this.hashmap = GlobalHashMap.getHashMap();
        System.out.println("got hashmap with size: " + hashmap.size());
        this.visited = new ArrayList<State>();
        this.disc = discID;/*
         *
         */
    }

    public HashMap<String, State> getHashMap() {
        return this.hashmap;
    }

    public void setHashmap(HashMap<String, State> hashmap) {
        this.hashmap = hashmap;
    }

    public void readMC(String fileName) {
        ObjectFileIO fileIO = new ObjectFileIO(fileName);
        MonteCarlo temp = (MonteCarlo) fileIO.readFile();
        this.setHashmap(temp.getHashMap());
    }

    public void saveMC(String fileName) {
        ObjectFileIO fileIO = new ObjectFileIO(fileName);
        fileIO.saveFiles(this);
    }

    public Square getNextMove(GameGrid currentGrid) {
        int bestRow = -1;
        int bestCol = -1;
        GameGrid tempGrid = new GameGrid();
        State bestState = null;
        double bestReward = -1.0;

        // System.out.println("hashmap.size " + hashmap.size());

        if (!withProb(alpha) && hashmap.size() >= 9) {
            // tries all possible moves and chooses the one that is stored with the highest estimated value
            System.out.println("custom move");
            for (int i = 0; i < GameGrid.GRID_ROWS; i++) {
                for (int j = 0; j < GameGrid.GRID_COLUMNS; j++) {
                    if (currentGrid.isValidMove(j, i)) {
                        // kolla om den är bättre isåna fall spara som den bästa hittills.
                        tempGrid.copyFromGrid(currentGrid);
                        tempGrid.addDisc(i, j, disc);
                        State tempState = hashmap.get(tempGrid.toString());
                        if (tempState != null) {
                            if (tempState.getValue() >= bestReward) {
                                bestReward = tempState.getValue();
                                bestRow = i;
                                bestCol = j;
                                bestState = tempState;
                            } else{
                                System.out.println("poor reward");
                            }
                        }
                    }
                }
            }
            
            
            if (bestRow < 0 || bestCol < 0) {
                // pick random
                System.out.println("cant find best stored making random move");
                boolean stop = false;
                for (int i = 0; i < GameGrid.GRID_ROWS && !stop; i++) {
                    for (int j = 0; j < GameGrid.GRID_COLUMNS && !stop; j++) {
                        if (currentGrid.isValidMove(j, i)) {
                            bestRow = i;
                            bestCol = j;
                            if (random.nextInt(15) > 12) {
                                stop = true;
                            }
                        }
                    }
                }
                tempGrid.copyFromGrid(currentGrid);
                tempGrid.addDisc(bestRow, bestCol, disc);
                if (hashmap.get(tempGrid.toString()) != null) {
                    System.out.println("got stored");
                    bestState = hashmap.get(tempGrid.toString());
                } else {
                    System.out.println("new move altogether");
                    bestState = new State(tempGrid);
                }
            }

            visited.add(bestState);
            /* System.out.println("hashmap size: " + hashmap.size());
            System.out.println(bestState.toString() + "\n\n");
            System.out.println("reward: " + bestState.getValue());/**/

            Square tempSq = GameGrid.columnThatDiffer(currentGrid, bestState.getGameGrid());
            if (tempSq == null) {
                System.out.println("tempSq is null... not good at all");
                System.exit(1);
            }
            return tempSq;
        } else {
            // make a exploritary move
            // pick random
            System.out.println("random move + " + alpha);
            boolean stop = false;
            for (int i = 0; i < GameGrid.GRID_ROWS && !stop; i++) {
                for (int j = 0; j < GameGrid.GRID_COLUMNS && !stop; j++) {
                    if (currentGrid.isValidMove(j, i)) {
                        bestRow = i;
                        bestCol = j;
                        if (random.nextInt(15) > 12) {
                            stop = true;
                        }
                    }
                }
            }
            if (tempGrid == null) {
                System.out.println("null tempGrid");
            } else if (currentGrid == null) {
                System.out.println("current grid null");
            }
            tempGrid.copyFromGrid(currentGrid);
            if (!tempGrid.addDisc(bestRow, bestCol, disc)) {
                System.out.println("can not add");
            }

            if (hashmap.get(tempGrid.toString()) != null) {
                bestState = hashmap.get(tempGrid.toString());
            } else {
                bestState = new State(tempGrid);
            }

            visited.add(bestState);
            // System.out.println("" + tempGrid.toString());
            Square tempSq = GameGrid.columnThatDiffer(bestState.getGameGrid(), currentGrid);
            if (tempSq == null) {
                System.out.println("tempSq is null... not good");
                System.exit(1);
            }
            return tempSq;
        }
    }

    /**
     * update all the visited states and sets the final states reward to the one
     * we send with it
     *
     * @param reward
     */
    public void updateStates(double reward) {
        /*
         * if(reward > 0){ System.out.println("winner"); } else if(reward < 0){
         * System.out.println("loser"); }else{ System.out.println("draw"); }/*
         */
        State updateTo;
        State updateFrom = visited.get(visited.size() - 1);
        updateFrom.setValue(reward);
        for (int i = visited.size() - 1; i > 0; i--) {
            updateFrom = visited.get(i);
            updateTo = visited.get(i - 1);
            // System.out.print("updating value from: " + updateTo.getValue());
            /*
             * System.out.println("from: " + updateFrom.toString());
             * System.out.println("to: " + updateTo.toString());/*
             */


            // System.out.println("update from value: " + updateFrom.getValue() + " to value: " + updateTo.getValue());
            updateTo.setValue(updateTo.getValue() + ((1 / (updateTo.getNrOfVisits() + 1.0)) * (updateFrom.getValue() - updateTo.getValue())));
            // System.out.println(" to: " + updateTo.getValue());
            // System.out.println("" + updateTo.toString());/**/
        }

        for (int i = 0; i < visited.size(); i++) {
            visited.get(i).visitState();
            hashmap.put(visited.get(i).toString(), visited.get(i));
        }

        visited = new ArrayList<State>();
        // saveMC(fileName);
    }

    public void decreaseLearningConstant() {
        if (alpha > 0) {
            alpha -= 0.01;
        }
    }

    /**
     * returns true with prob probability and false with 1-prob probability
     *
     * @param prob
     * @return
     */
    private boolean withProb(double prob) {
        if (random.nextInt(1000) < (prob * 1000)) {
            return true;
        } else {
            return false;
        }
    }

    public void saveHashMapToFile() {
        // saveMC(fileName);
        GlobalHashMap.saveHashMap();
    }
}
